@thi.ng/paths

This project is part of the
@thi.ng/umbrella monorepo.
About
This library provides immutable and mutable, optimized path-based
accessors for nested, vanilla JS objects & arrays with structural
sharing.
Installation
yarn add @thi.ng/paths
Dependencies
Usage
import * as paths from "@thi.ng/paths";
The getter()
, setter()
and updater()
functions compile a lookup
path like a.b.c
into a function operating directly at the value the
path points to in nested object. For getters, this essentially compiles
to val = obj.a.b.c
, with the important difference that the function
returns undefined
if any intermediate values along the lookup path are
undefined (and doesn't throw an error).
The resulting setter function too accepts a single object (or array) to
operate on and when called, immutably replaces the value at the
given path, i.e. it produces a selective deep copy of obj up until given
path. If any intermediate key is not present in the given object, it
creates a plain empty object for that missing key and descends further
along the path.
s = setter("a.b.c");
s = setter(["a","b","c"]);
s({a: {b: {c: 23}}}, 24)
s({x: 23}, 24)
s(null, 24)
Nested value updaters follow a similar pattern, but also take a user
supplied function to apply to the existing value (incl. any other
arguments passed):
inc = updater("a.b", (x) => x != null ? x + 1 : 1);
inc({a: {b: 10}});
inc({});
add = updater("a.b", (x, n) => x + n);
add({a: {b: 10}}, 13);
In addition to these higher-order functions, the module also provides
immediate-use wrappers: getIn()
, setIn()
, updateIn()
and
deleteIn()
. These functions are using getter
/ setter
internally,
so have same behaviors.
state = {a: {b: {c: 23}}};
getIn(state, "a.b.c")
setIn(state, "a.b.c", 24)
updateIn(state, "a.b.c", x => x + 1)
deleteIn(state, "a.b.c.")
Structural sharing
Only keys in the path will be updated, all other keys present in the
given object retain their original/identical values to provide efficient
structural sharing / re-use. This is the same behavior as in Clojure's
immutable maps or those provided by ImmutableJS (albeit those
implementation are completely different - they're using trees, we're
using the ES6 spread op (for objects, slice()
for arrays) and dynamic
functional composition to produce the setter/updater).
s = setter("a.b.c");
a = { x: { y: { z: 1 } }, u: { v: 2 } };
b = s(a, 3);
a.x === b.x
a.x.y === b.x.y
a.u === b.u;
Mutable setter
mutator()
is the mutable alternative to setter()
. It returns a
function, which when called, mutates given object / array at given path
location and bails if any intermediate path values are non-indexable
(only the very last path element can be missing in the actual object
structure). If successful, returns original (mutated) object, else
undefined
. This function too provides optimized versions for path
lengths <= 4.
As with setIn
, mutIn
is the immediate use mutator, i.e. the same as:
mutator(path)(state, val)
.
mutIn({ a: { b: [10, 20] } }, "a.b.1", 23);
mutIn({ a: { b: [10, 20] } }, ["a", "b", 1], 23);
mutIn({}, "a.b.c", 23);
Path checking
The exists()
function takes an arbitrary object and lookup path.
Descends into object along path and returns true if the full path exists
(even if final leaf value is null
or undefined
). Checks are
performed using hasOwnProperty()
.
exists({ a: { b: { c: [null] } } }, "a.b.c.0");
exists({ a: { b: { c: [null] } } }, "a.b.c.1");
Authors
License
© 2016 - 2018 Karsten Schmidt // Apache Software License 2.0